StaticFetcher() Implementation + from_pdb() refactor - #5436
Conversation
|
Docs need to be reupdated, but the code itself feel like it is in a completed state. |
Documentation build overview
10 files changed ·
|
yuxuanzhuang
left a comment
There was a problem hiding this comment.
I think the code is in pretty good shape. I will add more comments related to the code.
For the documentation, it would be helpful to add an overview page at package/doc/sphinx/source/documentation_pages/fetchers_modules.rst that includes:
- Usage examples.
- A list of the available fetchers.
See package/doc/sphinx/source/documentation_pages/converters.rst for an example.
|
|
||
| cache_files = [ | ||
| path | ||
| for path in self.cache_path.rglob("*") |
There was a problem hiding this comment.
Checking whether a requested file is present in the registry should not require scanning the entire cache directory. That is error-prone since unrelated cache files can also cause false failures; it also adds unnecessary I/O.
There was a problem hiding this comment.
check_registry() might be a misleading name. This method is meant to return all files not in the self.cache_path per the docstring
Return paths relative to :attr:`cache_path` for cache files that are
missing from the registry.
I originally made it when I was refactoring the fetch() method where it would toss error to prevent unintentional update of the registry (which would be a security issue)
if LOAD_FROM_CACHE:
registry_dictionary = self.read_registry(db_path)
missing_files_list = self.check_registry(
db_path, files=list(requested_files)
)
if len(missing_files_list) != 0:
MISSING_FILES = True
if MISSING_FILES and not APPEND_DATABASE:
raise ValueError(
"fetch() is requesting files not found in the registry. "
+ f"The missing files are {missing_files_list}. "
+ "To fix this, please set append_db=True to append the "
+ "registry."
)There was a problem hiding this comment.
This method could be renamed because it's not really checking like return a True or False
Co-authored-by: Yuxuan Zhuang <yuzhuang@stanford.edu>
Co-authored-by: Yuxuan Zhuang <yuzhuang@stanford.edu>
Co-authored-by: Yuxuan Zhuang <yuzhuang@stanford.edu>
Co-authored-by: Yuxuan Zhuang <yuzhuang@stanford.edu>
orbeckst
left a comment
There was a problem hiding this comment.
Please see my comments on StaticFetcher in #5457 (review) and apply them here.
There was a problem hiding this comment.
I added comments to PR #5457 as I hadn't realized that you're first introducin StaticFetcher here. Please see #5457 (review)
BradyAJohnston
left a comment
There was a problem hiding this comment.
Some mostly minor things around the place, but biggest thing is the return type.
I think it should be single item in -> single item out, sequence in -> sequence of Path out rather than always returning a list[Path].
| Path( | ||
| main_downloader.fetch( | ||
| fname=file_name, | ||
| progressbar=verbose, |
There was a problem hiding this comment.
verbose is currently not passed along properly. Pooch only reads the progressbar argument of Pooch.fetch() when no downloader is given (if downloader is None: downloader = choose_downloader(url, progressbar=progressbar)), and StaticFetcher always supplies one.
from_PDB(progressbar=True) only works because the unknown progressbar kwarg leaks through **kwargs into HTTPDownloader(progressbar=...).
Explicitly forward progressbar=verbose into the downloader constructor inside _set_downloader, having from_PDB call verbose=progressbar, and adding a test that asserts the downloader receives it.
| if APPEND_DATABASE and LOAD_FROM_CACHE: | ||
| self.append_registry(db_path, requested_files) | ||
|
|
||
| return paths[0] if len(paths) == 1 else paths |
There was a problem hiding this comment.
This does change the current return type compared to develop which always returned a list of Path items.
I think we should have single item / string in -> Path out, sequence in -> list[Path] out, otherwise people will constantly have to unpack the first item in the list to use it.
| registry_dictionary.setdefault(name, None) | ||
|
|
||
| # Download code using pooch | ||
| main_downloader = pooch.create( |
There was a problem hiding this comment.
A scenario worth thinking through: the registry stores the hash of whatever was first downloaded. If the upstream file later changes (PDB entries do get revised) and the cached copy has been evicted, pooch raises a hash mismatch. Before that, retry_if_failed retries the download twice with sleeps, which can never fix a hash mismatch. The only recovery is deleting hashes.txt, and pooch's error message doesn't say so.
Related: for from_PDB, which always passes append_db=True, the registry offers no protection against a bad server because the hash is derived from the download itself. It only detects local cache corruption. That's fine, but the docs should say so.
Suggestions: catch pooch's hash ValueError and re-raise with a recovery hint, and decide explicitly whether from_PDB needs a registry at all.
| for line in f: | ||
|
|
||
| # Pooch registry files can contain comments | ||
| if line.lstrip()[0] == "#": |
There was a problem hiding this comment.
The docstring promises the pooch registry format, but two valid pooch registry files fail here:
- A blank line (e.g. trailing newline after a hand edit) raises
IndexErroronline.lstrip()[0]. - The optional third URL column (
fname hash url) raisesValueError: too many values to unpackon the next line.
Skip empty lines (if not line.strip() or line.lstrip().startswith("#")) and take only the first two fields, or delegate to pooch's own load_registry.
| return [ | ||
| path | ||
| for path in cache_files | ||
| if (path.name not in database_files) and (path not in ignore) |
There was a problem hiding this comment.
ignore doesn't behave as documented. The docstring and example say entries are relative to cache_path (ignore=["file2.txt"]), but this compares against absolute Path objects from rglob, so a relative entry never matches. I confirmed ignore=["b.txt"] leaves b.txt in the result while ignore=[cache_path / "b.txt"] removes it. The test only passes absolute paths, which is why it passes.
Resolve ignore entries against cache_path the same way files is handled a few lines up.
| append_db=False, | ||
| downloader="auto", | ||
| **kwargs, | ||
| ): |
There was a problem hiding this comment.
Return and argument type hinting
|
|
||
| return paths[0] if len(paths) == 1 else paths | ||
|
|
||
| def append_registry(self, db_path, files, write_duplicate=False): |
|
|
||
| self.write_registry(Path(db_path), _new_files, mode="a") | ||
|
|
||
| def check_registry(self, db_path, files=None, ignore=None): |
| if (path.name not in database_files) and (path not in ignore) | ||
| ] | ||
|
|
||
| def read_registry(self, db_path): |
|
|
||
| return hash_dict | ||
|
|
||
| def write_registry(self, db_path, files, mode="w"): |
|
Ok, I just saw the comments today. I will spend time this weekend addressing them. |
Fixes #5429 and #5431
Changes made in this Pull Request:
fetch.fetchers.StaticFetcher()fetch.pdb.from_pdb()to useStaticFetchers()LLM / AI generated code disclosure
LLMs or other AI-powered tools (beyond simple IDE use cases) were used in this contribution: yes / no
From comment: #5436 (comment)
PR Checklist
package/CHANGELOGfile updated?package/AUTHORS? (If it is not, add it!)Developers Certificate of Origin
I certify that I can submit this code contribution as described in the Developer Certificate of Origin, under the MDAnalysis LICENSE.